import json
import re

from cvss import CVSS3

from dojo.models import Finding


class JFrogXrayParser:

    """JFrog Xray JSON reports"""

    def get_scan_types(self):
        return ["JFrog Xray Scan"]

    def get_label_for_scan_types(self, scan_type):
        return scan_type

    def get_description_for_scan_types(self, scan_type):
        return "Import Xray findings in JSON format."

    def get_findings(self, json_output, test):
        tree = json.load(json_output)
        return self.get_items(tree, test)

    def get_items(self, tree, test):
        items = {}
        if "data" in tree:
            vulnerabilityTree = tree["data"]

            for node in vulnerabilityTree:
                item = get_item(node, test)

                title_cve = "No CVE"
                more_details = node.get("component_versions").get(
                    "more_details",
                )
                if "cves" in more_details:
                    if "cve" in more_details.get("cves")[0]:
                        title_cve = (
                            node.get("component_versions")
                            .get("more_details")
                            .get("cves")[0]
                            .get("cve")
                        )

                unique_key = (
                    node.get("id")
                    + node.get("summary")
                    + node.get("provider")
                    + node.get("source_comp_id")
                    + title_cve
                )
                items[unique_key] = item

        return list(items.values())


def decode_cwe_number(value):
    match = re.match(r"CWE-\d+", value, re.IGNORECASE)
    if match is None:
        return 0
    return int(match[0].rsplit("-")[1])


def get_item(vulnerability, test):
    # Following the CVSS Scoring per https://nvd.nist.gov/vuln-metrics/cvss
    if "severity" in vulnerability:
        severity = "Info" if vulnerability["severity"] == "Unknown" else vulnerability["severity"].title()
    # TODO: Needs UNKNOWN new status in the model.
    else:
        severity = "Info"

    vulnerability_ids = []
    cwe = None
    cvssv3 = None
    cvss_v3 = "No CVSS v3 score."
    mitigation = None
    fix_available = False
    extra_desc = ""
    # Some entries have no CVE entries, despite they exist. Example
    # CVE-2017-1000502.
    cves = vulnerability["component_versions"]["more_details"].get("cves", [])
    if len(cves) > 0:
        vulnerability_ids.extend(item.get("cve") for item in cves if item.get("cve"))
        # take only the first one for now, limitation of DD model.
        if len(cves[0].get("cwe", [])) > 0:
            cwe = decode_cwe_number(cves[0].get("cwe", [])[0])
        if "cvss_v3" in cves[0]:
            cvss_v3 = cves[0]["cvss_v3"]
            # this dedicated package will clean the vector
            cvssv3 = CVSS3.from_rh_vector(cvss_v3).clean_vector()

    if "fixed_versions" in vulnerability["component_versions"]:
        mitigation = "**Versions containing a fix:**\n"
        mitigation += "\n".join(
            vulnerability["component_versions"]["fixed_versions"],
        )
        fix_available = True

    if "vulnerable_versions" in vulnerability["component_versions"]:
        extra_desc = "\n**Versions that are vulnerable:**\n"
        extra_desc += "\n".join(
            vulnerability["component_versions"]["vulnerable_versions"],
        )

    provider = (
        vulnerability.get("component_versions")
        .get("more_details")
        .get("provider")
    )
    if provider:
        extra_desc += f"\n**Provider:** {provider}"

    component_name = vulnerability.get("component")
    component_version = vulnerability.get("source_comp_id")[
        len(vulnerability.get("source_id", "")) + 1:
    ]

    # The 'id' field is empty? (at least in my sample file)
    if vulnerability_ids:
        if vulnerability["id"]:
            title = (
                vulnerability["id"]
                + " - "
                + str(vulnerability_ids[0])
                + " - "
                + component_name
                + ":"
                + component_version
            )
        else:
            title = (
                str(vulnerability_ids[0])
                + " - "
                + component_name
                + ":"
                + component_version
            )
    elif vulnerability["id"]:
        title = (
            vulnerability["id"]
            + " - "
            + component_name
            + ":"
            + component_version
        )
    else:
        title = "No CVE - " + component_name + ":" + component_version

    # create the finding object
    finding = Finding(
        title=title,
        cwe=cwe,
        test=test,
        severity=severity,
        description=(vulnerability["summary"] + extra_desc).strip(),
        mitigation=mitigation,
        component_name=component_name,
        component_version=component_version,
        file_path=vulnerability.get("source_comp_id"),
        static_finding=True,
        dynamic_finding=False,
        cvssv3=cvssv3,
        fix_available=fix_available,
    )
    if vulnerability_ids:
        finding.unsaved_vulnerability_ids = vulnerability_ids
    return finding
